package com.puff.ioc;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

import javax.tools.DiagnosticCollector;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;

public enum JDKCompiler {
	INSTANCE;
	private final JavaCompiler compiler;

	private final List<String> options;

	private DiagnosticCollector<JavaFileObject> diagnostics;

	private final JavaFileManager javaFileManager;

	private CompilerClassloader classLoader;

	JDKCompiler() {
		compiler = ToolProvider.getSystemJavaCompiler();

		if (compiler == null) {
			throw new IllegalStateException("Cannot find the system Java compiler. " + "Check that your class path includes tools.jar");
		}

		this.classLoader = new CompilerClassloader(this.getClass().getClassLoader());
		diagnostics = new DiagnosticCollector<JavaFileObject>();
		final StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);

		ClassLoader loader = this.classLoader.getParent();
		List<String> paths = getClassPath(loader);
		List<File> cpFiles = new ArrayList<File>();
		if (paths != null && (!paths.isEmpty())) {
			for (String file : paths) {
				cpFiles.add(new File(file));
			}
		}
		try {
			fileManager.setLocation(StandardLocation.CLASS_PATH, cpFiles);
		} catch (IOException e) {
			e.printStackTrace();
		}

		if (loader instanceof URLClassLoader && (!loader.getClass().getName().equals("sun.misc.Launcher$AppClassLoader"))) {
			try {
				@SuppressWarnings("resource")
				URLClassLoader urlClassLoader = (URLClassLoader) loader;
				List<File> path = new ArrayList<File>();
				for (URL url : urlClassLoader.getURLs()) {
					File file = new File(url.getFile());
					path.add(file);
				}
				fileManager.setLocation(StandardLocation.CLASS_PATH, path);
			} catch (IOException e) {
				e.printStackTrace();
			}
		} else {
			Enumeration<URL> resources = null;
			try {
				resources = loader.getResources("/");
			} catch (IOException e) {
				e.printStackTrace();
			}
			if (resources != null) {
				List<File> path = new ArrayList<File>();
				while (resources.hasMoreElements()) {
					URL resource = resources.nextElement();
					File file = new File(resource.getFile());
					path.add(file);
				}
			}

		}

		javaFileManager = new ForwardingJavaFileManager<JavaFileManager>(fileManager) {
			@Override
			public JavaFileObject getJavaFileForOutput(Location location, String qualifiedName, Kind kind, FileObject outputFile) throws IOException {
				// 由于编译成功后的bytecode需要放到file中，所以需要将file放到classloader中，以便读取bytecode生成Class对象.
				classLoader.add(qualifiedName, outputFile);
				return (JavaFileObject) outputFile;
			}
		};
		this.options = new ArrayList<String>();
	}

	public static List<String> getClassPath(ClassLoader cl) {
		List<String> paths = new ArrayList<String>();
		while (cl != null) {
			boolean isUrlClassloader = cl instanceof URLClassLoader;
			if (isUrlClassloader) {
				URLClassLoader urlClassLoader = (URLClassLoader) cl;
				for (URL url : urlClassLoader.getURLs()) {
					paths.add(url.getFile());
				}
			} else {
				Enumeration<URL> resources = null;
				try {
					resources = cl.getResources("/");
				} catch (IOException e) {
					e.printStackTrace();
				}
				if (resources != null) {
					while (resources.hasMoreElements()) {
						URL resource = resources.nextElement();
						paths.add(resource.getFile());
					}
				}
			}
			cl = cl.getParent();
		}
		return paths;
	}

	public synchronized Class<?> compileToClass(final JavaSource src) {
		List<JavaFileObject> compileSrcs = new ArrayList<JavaFileObject>();
		String className = src.getSimpleName();
		final JavaFileObject compileSrc = new ProxyJavaFileObject(className, src.getSource());
		compileSrcs.add(compileSrc);
		final CompilationTask task = compiler.getTask(null, javaFileManager, diagnostics, options, null, compileSrcs);
		final Boolean result = task.call();
		if (result == null || !result.booleanValue()) {
			throw new RuntimeException(src.getSource() + "\n" + diagnostics.getDiagnostics().toString());
		}
		try {
			return loadClass(src.getName());
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return null;
	}

	public Class<?> loadClass(final String qualifiedClassName) throws ClassNotFoundException {
		return (Class<?>) classLoader.loadClass(qualifiedClassName);
	}

	static URI toUri(String name) {
		try {
			return new URI(name);
		} catch (URISyntaxException e) {
			throw new RuntimeException(e);
		}
	}

}
